package pwd.allen.algorithm;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * 自定义精确算法：根据日期分表
 * @author allen
 * @create 2022-08-14 17:50
 **/
@Slf4j
public class DateAlgorithm implements PreciseShardingAlgorithm<Date>, RangeShardingAlgorithm<Date> {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyMM");

    /**
     * 精确算法处理逻辑
     * @param availableTargetNames
     * @param shardingValue
     * @return
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {

        // 这里拿到的不一定是Date类型，也可能是String或者Timestamp类型
        Object value = shardingValue.getValue();

        // 有效的目标表
        log.info("有效的目标表：{}", availableTargetNames);

        // 打印一些信息
        // DateAlgorithm是否单实例：是
        log.info("【{}】表名:{},字段名:{},字段取值:{}", this.hashCode(), shardingValue.getLogicTableName(), shardingValue.getColumnName(), value);

        String tbName = String.format("%s_%s", shardingValue.getLogicTableName(), simpleDateFormat.format(value));

        if (availableTargetNames.contains(tbName)) {
            return tbName;
        }

        throw new IllegalArgumentException("更具日期分表失败");
    }

    /**
     * 范围算法处理逻辑
     * @param availableTargetNames
     * @param shardingValue
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Date> shardingValue) {
        Range<Date> range = shardingValue.getValueRange();
        Date lowerDate = range.lowerEndpoint();
        Date upperDate = range.upperEndpoint();

        log.info("lowerDate:{}  upperDate:{}", lowerDate, upperDate);

        final DateRange dateRange = new DateRange();
        if (lowerDate != null) {
            dateRange.setLow(Convert.toInt(DateUtil.format(lowerDate, "yyMM")));
        }
        if (upperDate != null) {
            dateRange.setUpper(Convert.toInt(DateUtil.format(upperDate, "yyMM")));
        }

        return availableTargetNames.stream().filter(name -> {
            Integer yyMM = Convert.toInt(name.substring(name.length() - 4, name.length()));
            boolean b = true;
            if (dateRange.getLow() != null && yyMM < dateRange.getLow()) {
                b = false;
            }
            if (dateRange.getUpper() != null && yyMM > dateRange.getUpper()) {
                b = false;
            }
            return b;
        }).collect(Collectors.toSet());
    }

    @Data
    class DateRange {
        private Integer low;
        private Integer upper;
    }
}
